package com.mnemonic;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;

import com.mnemonic.audio.BlastAudio;
import com.mnemonic.audio.ExplodeEffect;
import com.mnemonic.audio.GameAudio;
import com.mnemonic.audio.GameOver;
import com.mnemonic.domain.Game;
import com.mnemonic.domain.Player;
import com.mnemonic.domain.RobotPlayer;
import com.mnemonic.domain.Turn;
import com.mnemonic.utils.DateUtils;

/**
 * 游戏页面
 * 
 * @author lifeng
 * @date 2021/05/16
 */
public class GamePage extends JDialog {

    private static final long serialVersionUID = 1L;

    /**
     * 游戏数据
     */
    private final Game game;

    /**
     * 每个单元格的大小
     */
    protected int size = 20;

    /**
     * 左上角
     */
    private JTextField gameClock;
    private JTextField mineCount;
    private JTextField boutCount;
    private JTextField turnClock;
    private JTextField turnCount;

    /**
     * 当前是否透视
     */
    private volatile boolean penetrateFlag = false;
    private MinePanel minePanel;
    private PlayersPanel playersPanel;

    /**
     * 爆炸效果
     */
    private List<ExplodeEffect> explodes;

    /**
     * 制作一个计时器
     */
    private Thread timer;
    private int times;

    /**
     * game 是否运行
     */
    private volatile boolean running = true;

    /**
     * 操作的数量
     */
    private int operates;

    /**
     * 创建一个弹出窗口
     * 
     * @param root
     *            -- 设置此参数可以控制弹出窗口总是在指定的窗口之前
     * @param game
     */
    public GamePage(JFrame root, Game game) {
        super(root, false);
        this.game = game;
        this.setTitle(game.getName() + "  正在进行中...");
        Rectangle rectangle = root.getBounds();
        this.setBounds(rectangle.x - 35, rectangle.y + 30, rectangle.width + 70, rectangle.height);
        this.setResizable(false);
        this.setLayout(null);
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                int result = Dialogs.confirm("提醒", "确认关闭？");
                if (result == 0) {
                    GamePage.this.destory();
                }
            }
        });

        // 本场时间计数器
        gameClock = new JTextField();
        gameClock.setText("运行：");
        gameClock.setEditable(false);
        gameClock.setBackground(Color.white);
        gameClock.setHorizontalAlignment(JTextField.CENTER);
        gameClock.setBounds(10, 10, 100, 30);
        this.add(gameClock);

        // 雷数
        mineCount = new JTextField();
        mineCount.setText("雷数：");
        mineCount.setEditable(false);
        mineCount.setBackground(Color.white);
        mineCount.setHorizontalAlignment(JTextField.CENTER);
        mineCount.setBounds(120, 10, 100, 30);
        this.add(mineCount);

        // 次数计时器
        turnClock = new JTextField();
        turnClock.setText("剩余次数：");
        turnClock.setEditable(false);
        turnClock.setBackground(Color.white);
        turnClock.setHorizontalAlignment(JTextField.CENTER);
        turnClock.setBounds(230, 10, 100, 30);
        this.add(turnClock);

        // 此回合剩余次数
        turnCount = new JTextField();
        turnCount.setText("次数：");
        turnCount.setEditable(false);
        turnCount.setBackground(Color.white);
        turnCount.setHorizontalAlignment(JTextField.CENTER);
        turnCount.setBounds(340, 10, 100, 30);
        this.add(turnCount);

        // 回合数
        boutCount = new JTextField();
        boutCount.setText("回合：");
        boutCount.setEditable(false);
        boutCount.setBackground(Color.white);
        boutCount.setHorizontalAlignment(JTextField.CENTER);
        boutCount.setBounds(450, 10, 100, 30);
        this.add(boutCount);

        // 透视按钮
        JButton penetrateOps = new JButton("透视");
        penetrateOps.addActionListener((e) -> {
            this.game.burnMine(0, 0);
            this.penetrateFlag = !this.penetrateFlag;
        });
        penetrateOps.setBounds(625, 10, 80, 30);
        this.add(penetrateOps);

        // 存档按钮
        JButton saveOps = new JButton("存档");
        saveOps.addActionListener((e) -> {
            Dialogs.info("操作提醒", "存档成功！");
        });
        saveOps.setBounds(715, 10, 80, 30);
        this.add(saveOps);

        // 结束
        JButton finishOps = new JButton("结束");
        finishOps.addActionListener((e) -> {
            int result = Dialogs.confirm("提醒", "确认关闭？");
            if (result == 0) {
                GamePage.this.destory();
            }
        });
        finishOps.setBounds(805, 10, 80, 30);
        this.add(finishOps);

        // 雷区绘制的 panel
        minePanel = new MinePanel();
        minePanel.setBounds(10, 50, 610, 415);
        this.add(minePanel);

        // 玩家的 panel
        playersPanel = new PlayersPanel(this.game.getPlayers());
        playersPanel.setBounds(620, 50, 270, 415);
        this.add(playersPanel);

        // 爆炸效果
        this.explodes = new CopyOnWriteArrayList<>();

        // 制作一个心跳
        this.timer = new Thread(() -> {
            while (running) {
                // 计数
                times++;

                try {
                    GamePage.this.heartbeat();
                } catch (Exception e) {
                }

                // 每1秒运行一次
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }
        });
        this.timer.setDaemon(true);

        // 开启程序
        this.start();
    }

    /**
     * 开启游戏：<br>
     * 1. 开启计时器, 倒计时<br>
     * 2. 初始化雷区<br>
     * 3. 开启第一回合<br>
     * 4. 开启背景音乐<br>
     */
    private void start() {

        // 1. 开启计时器, 倒计时, 启动后每秒会执行一次 heartbeat
        this.timer.start();

        // 3. 开启回合
        this.nextTurn();

        // 4. 开启背景音乐
        new GameAudio("start.wav").start();
    }

    /**
     * 心跳处理： 每秒处理
     */
    private void heartbeat() {

        // 设置计时器的显示
        this.gameClock.setText("运行：" + DateUtils.formatLongTime(times * 1000, null));

        // 倒计时的处理, 此处判断是否启用了倒计时
        if (this.game.getLevel().getBoutTimeOut() != null && this.game.getLevel().getBoutTimeOut() > 0) {

            // 剩余的时间数
            int remain = this.game.getLevel().getBoutTimeOut() - this.times % this.game.getLevel().getBoutTimeOut();

            // 设置计时器的显示
            this.turnClock.setText("剩余时间：" + (remain == 0 ? "0秒" : DateUtils.formatLongTime(remain * 1000, null)));

            // 开启下一轮回
            if (remain == 0) {
                this.nextTurn();
            }
        }
    }

    /**
     * 关闭窗体
     */
    private void destory() {
        this.running = false;
        this.minePanel.destory();
        new GameOver().start();
        try {
            Thread.sleep(1800);
        } catch (InterruptedException e) {
        }
        this.dispose();
    }

    /**
     * 扫雷成功
     */
    private void mine_success(int row, int col) {
        new BlastAudio().start();
        this.turn.getPlayer().addScores();
        this.playersPanel.update(this.turn.getPlayer());
        this.mine_judge(row, col);
        this.checkMine();
    }

    /**
     * 扫雷失败
     */
    private void mine_fail(int row, int col) {
        int x = col * size + 40;
        int y = row * size + 50;
        this.explodes.add(new ExplodeEffect(this, x, y));
        this.turn.getPlayer().subScores();
        this.playersPanel.update(this.turn.getPlayer());
        this.mine_judge(row, col);
        this.checkMine();
    }

    /**
     * 空白区域的操作
     */
    private void mine_void(int row, int col) {
        new BlastAudio().start();
        this.mine_judge(row, col);
    }

    /**
     * 扫雷失误
     */
    private void mine_mistake(int row, int col) {
        this.turn.getPlayer().addFaults();
        this.playersPanel.update(this.turn.getPlayer());
        new BlastAudio().start();
        this.mine_judge(row, col);
    }

    /**
     * 做一些有兴趣的操作
     */
    private void mine_judge(int row, int col) {
        this.operates++;
        int remian = this.game.getLevel().getBoutOps() - this.operates % this.game.getLevel().getBoutOps();
        this.turnCount.setText("剩余次数：" + remian);
        if (remian == this.game.getLevel().getBoutOps()) {
            this.nextTurn();
        }
    }

    /**
     * 校验结果
     */
    private void checkMine() {

    }

    /**
     * 1. 创建回合数据<br>
     * 2. 重置回合可操作数<br>
     * 3. 激活当前的玩家<br>
     * 4. 机器人自动操作<br>
     */
    private void nextTurn() {

        // 1. 创建回合数据
        this.turn = this.game.newTurn();

        // 2. 重置回合可操作数
        this.turnCount.setText("剩余次数：" + (turn.getMaxops() - turn.getOps()));

        // 3. 回合数
        this.boutCount.setText("第 " + turn.getBout() + "回合");

        // 3. 激活当前的玩家
        this.playersPanel.active(turn.getPlayer());

        // 4. 机器人自动操作

    }

    /**
     * 删除指定的爆炸
     * 
     * @param explode
     */
    public void removeExplode(ExplodeEffect explode) {
        this.explodes.remove(explode);
    }

    /**
     * 雷区: 只是写了绘制的代码, 并注册了相应的点击事件，将点击结果转化为排雷结果 <br>
     * mine_success<br>
     * mine_fail<br>
     * 
     * @author lifeng
     * @date 2021/05/16
     */
    public class MinePanel extends JPanel {

        private static final long serialVersionUID = 1L;
        private Thread rptimer;
        private volatile boolean running = true;

        public MinePanel() {
            this.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    MinePanel.this.opsMouseClicked(e);
                }
            });
            // 一直重绘
            this.rptimer = new Thread(() -> {
                while (running) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e1) {
                    }
                    this.repaint();
                }
            });
            this.rptimer.setDaemon(true);
            this.rptimer.start();
        }

        /**
         * 雷区的点击事件
         */
        private void opsMouseClicked(MouseEvent e) {

            // 游戏数据
            int rows = game.getLevel().getRow();
            int cols = game.getLevel().getCol();
            int[][] mineMap = game.getMineMap();

            // 雷区
            int row = (e.getY() - 50) / size;
            int col = (e.getX() - 4) / size;

            // 其他区域
            if (row * col < 0 || row > rows - 1 || col > cols - 1) {
                return;
            }

            // 双击事件
            if (e.getClickCount() == 2) {
                return;
            }

            // 这个分支是滚轮 不用处理
            if (e.getButton() == 2) {
                return;
            }

            // 已经是确定的结果了，不需要处理
            if ((mineMap[row][col] & 0b00000110) == 0b00000010) {
                return;
            }

            // 布雷,只有第一次会执行里面的代码
            GamePage.this.game.burnMine(row, col);

            // 左键点击
            if (e.getButton() == 1) {
                this.opsMouseLeftClicked(row, col);
            }
            // 右键点击
            else if (e.getButton() == 3) {
                this.opsMouseRightClicked(row, col);
            }
            // 重绘雷区
            this.repaint();
        }

        // 雷区的左键点击事件
        private void opsMouseLeftClicked(int row, int col) {

            // 雷区数据
            int[][] mineMap = game.getMineMap();

            // 左键不需要处理 插旗
            if ((mineMap[row][col] & 0b00000110) == 0b00000100) {
                return;
            }

            // 对问号的处理
            if ((mineMap[row][col] & 0b00000110) == 0b00000110) {

                // 减去问号
                mineMap[row][col] -= 0b00000100;

                // 有雷
                if ((mineMap[row][col] & 0b00000001) == 0b00000001) {
                    // 触雷的数据
                    GamePage.this.mine_fail(row, col);
                    return;
                }

                // 无雷区域的操作
                this.openWhiteOption(row, col);
                GamePage.this.mine_void(row, col);
                return;
            }

            // 触雷
            if ((mineMap[row][col] & 0b00000001) == 0b00000001) {

                // 显示雷
                mineMap[row][col] += 0b00000010;

                // 触雷的数据
                GamePage.this.mine_fail(row, col);
                return;
            }

            // 无雷区域的操作
            this.openWhiteOption(row, col);
            GamePage.this.mine_void(row, col);
        }

        // 雷区的右键点击事件
        private void opsMouseRightClicked(int row, int col) {

            // 雷区数据
            int[][] mineMap = game.getMineMap();

            // 右键为雷
            if ((mineMap[row][col] & 0b00000111) == 0b00000001) {
                mineMap[row][col] += 0b00000100;
                GamePage.this.mine_success(row, col);
            }

            // 右键没有雷
            else if ((mineMap[row][col] & 0b00000111) == 0b00000000) {
                mineMap[row][col] += 0b00000010;
                this.openWhiteOption(row, col);
                GamePage.this.mine_mistake(row, col);
            }

            // 其他情况不需要处理
        }

        // 级联操作
        private void openWhiteOption(int x, int y) {
            int rows = game.getLevel().getRow();
            int cols = game.getLevel().getCol();
            int[][] mineMap = game.getMineMap();
            int state = mineMap[x][y];

            if ((state & 0b1) == 1 || (state & 0b110) == 0b010 || (state & 0b110) == 0b100
                || (state & 0b110) == 0b110) {
                return;
            }

            mineMap[x][y] += 0b10;

            if ((state >>> 3) == 0) {
                if (x - 1 >= 0) {
                    openWhiteOption(x - 1, y);
                    if (y - 1 >= 0) {
                        openWhiteOption(x - 1, y - 1);
                    }

                    if (y + 1 < cols) {
                        openWhiteOption(x - 1, y + 1);
                    }
                }

                if (x + 1 < rows) {
                    openWhiteOption(x + 1, y);

                    if (y - 1 >= 0) {
                        openWhiteOption(x + 1, y - 1);
                    }

                    if (y + 1 < cols) {
                        openWhiteOption(x + 1, y + 1);
                    }
                }

                if (y - 1 >= 0) {
                    openWhiteOption(x, y - 1);
                }

                if (y + 1 < cols) {
                    openWhiteOption(x, y + 1);
                }
            }
        }

        /**
         * 绘制雷区
         */
        @Override
        public void paint(Graphics g) {
            super.paint(g);

            // 画背景，不是必须的
            this.doPaintBg(g);

            // 绘制雷区 透视
            if (GamePage.this.penetrateFlag) {
                this.doPenetratePaint(g);

            }
            // 绘制雷区 非透视
            else {
                this.doPaint(g);
            }

            // 绘制爆炸效果
            this.doPaintExplodes(g);
        }

        private void doPaintBg(Graphics g) {
            Rectangle rectangle = this.getBounds();
            g.drawImage(Images.mineBg, 0, 0, rectangle.width, rectangle.height, this);
        }

        private void doPenetratePaint(Graphics g) {
            int rows = game.getLevel().getRow();
            int cols = game.getLevel().getCol();
            int[][] mineMap = game.getMineMap();

            int j = 0;
            for (int i = 0; i < rows; ++i) {
                for (j = 0; j < cols; ++j) {
                    if ((mineMap[i][j] & 0b00000001) == 1) {
                        if ((mineMap[i][j] & 0b00000110) == 0b00000100) {
                            g.drawImage(Images.mineImage, j * size + 4, i * size + 50, size, size, this);
                        } else {
                            g.drawImage(Images.bombImage, j * size + 4, i * size + 50, size, size, this);
                        }
                    } else if (((mineMap[i][j] >>> 3) == 0)) {
                        g.drawImage(Images.images[0], j * size + 4, i * size + 50, size, size, this);
                    } else {
                        g.drawImage(Images.images[mineMap[i][j] >>> 3], j * size + 4, i * size + 50, size, size, this);
                    }
                }
            }

            g.setColor(Color.gray);
            for (int i = 0; i <= rows; ++i) {
                g.drawLine(4, i * size + 50, cols * size + 4, i * size + 50);
            }

            for (int i = 0; i < cols; ++i) {
                g.drawLine(i * size + 4, 50, i * size + 4, rows * size + 50);
            }
        }

        private void doPaint(Graphics g) {
            int rows = game.getLevel().getRow();
            int cols = game.getLevel().getCol();
            int[][] mineMap = game.getMineMap();

            int j = 0;
            for (int i = 0; i < rows; ++i) {
                for (j = 0; j < cols; ++j) {
                    if ((mineMap[i][j] & 0b00000110) == 0) {
                        g.drawImage(Images.initImage, j * size + 4, i * size + 50, size, size, this);
                    } else if ((mineMap[i][j] & 0b00000110) == 0b00000010) {
                        if ((mineMap[i][j] & 0b00000001) == 1) {
                            g.drawImage(Images.mineImage, j * size + 4, i * size + 50, size, size, this);
                        } else if (((mineMap[i][j] >>> 3) == 0)) {
                            g.drawImage(Images.images[0], j * size + 4, i * size + 50, size, size, this);
                        } else {
                            g.drawImage(Images.images[mineMap[i][j] >>> 3], j * size + 4, i * size + 50, size, size,
                                this);
                        }
                    } else if ((mineMap[i][j] & 0b00000110) == 0b000000100) {
                        g.drawImage(Images.flagImage, j * size + 4, i * size + 50, size, size, this);
                    } else if ((mineMap[i][j] & 0b00000110) == 0b000000110) {
                        g.drawImage(Images.questionImage, j * size + 4, i * size + 50, size, size, this);
                    }
                }
            }

            g.setColor(Color.gray);
            for (int i = 0; i <= rows; ++i) {
                g.drawLine(4, i * size + 50, cols * size + 4, i * size + 50);
            }

            for (int i = 0; i < cols; ++i) {
                g.drawLine(i * size + 4, 50, i * size + 4, rows * size + 50);
            }
        }

        private void doPaintExplodes(Graphics g) {
            for (ExplodeEffect field : explodes) {
                field.paint(g);
            }
        }

        public void destory() {
            this.running = false;
        }
    }

    /**
     * 玩家的显示区域
     * 
     * @author lifeng
     * @date 2021/05/16
     */
    public class PlayersPanel extends JPanel {

        private static final long serialVersionUID = 1L;

        private List<PlayerPanel> players;

        public PlayersPanel(List<Player> players) {
            this.setLayout(new FlowLayout(FlowLayout.LEFT));
            List<PlayerPanel> fields = new ArrayList<>();
            for (Player player : players) {
                PlayerPanel field = new PlayerPanel(player);
                fields.add(field);
                this.add(field);
            }
            this.players = fields;
        }

        /**
         * 激活当前的玩家
         * 
         * @param player
         */
        public void active(Player player) {
            for (PlayerPanel field : players) {
                field.inactive();
                if (field.player.getId().equals(player.getId())) {
                    field.active();
                }
            }
        }

        public void update(Player player) {
            for (PlayerPanel field : players) {
                if (field.player.getId().equals(player.getId())) {
                    field.update();
                }
            }
        }
    }

    /**
     * 
     * 玩家的显示区域
     * 
     * @author lifeng
     * @date 2021/05/16
     */
    public class PlayerPanel extends JPanel {
        private static final long serialVersionUID = 1L;
        private Player player;
        private Color bg;

        private JLabel scores;
        private JLabel faults;

        public PlayerPanel(Player player) {
            this.player = player;
            this.setLayout(null);
            this.setPreferredSize(new Dimension(260, 60));

            // 图像
            JLabel logo_image = new JLabel();
            ImageIcon _logo_image = new ImageIcon(MainPage.class.getResource("images/" + player.getImage()));
            _logo_image.setImage(_logo_image.getImage().getScaledInstance(50, 50, Image.SCALE_DEFAULT));
            logo_image.setAlignmentX(Component.CENTER_ALIGNMENT);
            logo_image.setHorizontalAlignment(SwingConstants.CENTER);
            logo_image.setIcon(_logo_image);
            logo_image.setBounds(5, 5, 50, 50);
            this.add(logo_image);

            // 姓名
            JLabel name = new JLabel((player instanceof RobotPlayer ? "【机器人】" : "【普通玩家】") + player.getName());
            name.setBounds(60, 10, 200, 20);
            this.add(name);

            // 等级
            JLabel grade = new JLabel("等级：" + player.getGrade().name());
            grade.setBounds(65, 30, 75, 20);
            this.add(grade);

            // 此局的得分
            scores = new JLabel("得分：" + player.getScores());
            scores.setBounds(140, 30, 65, 20);
            this.add(scores);

            // 此局的失误数
            faults = new JLabel("失误：" + player.getFaults());
            faults.setBounds(205, 30, 65, 20);
            this.add(faults);

            // 设置边框
            this.setBorder(BorderFactory.createRaisedBevelBorder());

            // 原始背景
            this.bg = this.getBackground();
        }

        public void inactive() {
            this.setBackground(this.bg);
            this.repaint();
            this.revalidate();
            this.updateUI();
        }

        public void active() {
            this.setBackground(Color.RED);
            this.repaint();
            this.revalidate();
            this.updateUI();
        }

        public void update() {
            this.scores.setText("得分：" + player.getScores());
            this.faults.setText("失误：" + player.getFaults());
        }
    }
}
